﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Notoric.Model.Internal;

namespace Notoric.Model.Packaging
{
    /// <summary>
    /// Represents a container that can store data of a Notoric project.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Package is used to organize objects contained in a Notoric project into a single entity 
    /// of a defined physical format for portability and efficient access.
    /// </para>
    /// <para>
    /// The internal structure of a package is based on Open Packaging Conventions (OPC).
    /// For more information see the OPC specification available for download at http://go.microsoft.com/fwlink/?LinkID=71255.
    /// </para>
    /// </remarks>
    public sealed class Package : IDisposable, INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Package"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.Package"/>
        /// </para>
        /// <para>
        /// An opened package that will implement the core functionality of this package.
        /// </para>
        /// </param>
        /// <param name="fileStream">
        /// <para>
        /// Type: <see cref="System.IO.Stream"/>
        /// </para>
        /// <para>
        /// The stream to the package file, if the implementation package was opened using a file stream.
        /// This parameter is optional.
        /// </para>
        /// </param>
        private Package(System.IO.Packaging.Package package, System.IO.Stream fileStream = null)
        {
            this.package = package;
            this.fileStream = fileStream;

            parts = new Dictionary<Uri, PackagePart>();
            idAuthority = new IDAuthority();

            // Create or recreate the one and only instance of the project part
            System.IO.Packaging.PackagePart projPart = null;
            bool createNew = false;
            if (!package.PartExists(PackageVocabulary.ProjectPartUri))
            {
                projPart = CreatePartCore(
                    PackageVocabulary.ProjectPartUri, 
                    PackageVocabulary.ProjectPartContentType, 
                    PackageVocabulary.ProjectPartCompression, 
                    PackageVocabulary.ProjectPackageRelationshipType
                    );
                createNew = true;
            }
            else
                projPart = package.GetPart(PackageVocabulary.ProjectPartUri);
            projectPart = new ProjectPart(this, projPart, createNew);
            parts.Add(projectPart.Uri, projectPart);
        }

        #endregion

        #region IDisposable Members

        /// <inheritdoc/>
        public void Dispose()
        {
            Flush();
            package.Close();
            if (fileStream != null)
                fileStream.Close();
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when the value of some property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Private Methods

        /// <summary>
        /// Copies the data from a source stream to a destination stream.
        /// </summary>
        /// <param name="source">Source stream</param>
        /// <param name="dest">Destination stream</param>
        private void CopyStream(System.IO.Stream source, System.IO.Stream dest)
        {
            int bufCapacity = (int)Math.Min(source.Length, 4194304);    // Maximal buffer capacity = 4MB
            var buf = new byte[bufCapacity];
            int bytesRead = 0;

            while ((bytesRead = source.Read(buf, 0, bufCapacity)) > 0)
                dest.Write(buf, 0, bytesRead);
        }

        /// <summary>
        /// Creates a package part and the relationship between the package and the new part in the implementation package.
        /// </summary>
        /// <param name="partUri">
        /// <para>
        /// Type: <see cref="Uri"/>
        /// </para>
        /// <para>
        /// The URI of the part.
        /// </para>
        /// </param>
        /// <param name="contentType">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The type of the part's content.
        /// </para>
        /// </param>
        /// <param name="compressionOption">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.CompressionOption"/>
        /// </para>
        /// <para>
        /// The compression type of the part's content.
        /// </para>
        /// </param>
        /// <param name="relationshipType">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The type of the relationship between the package and the part.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// The created part.
        /// </para>
        /// </returns>
        private System.IO.Packaging.PackagePart CreatePartCore(Uri partUri, string contentType, 
            System.IO.Packaging.CompressionOption compressionOption, string relationshipType)
        {
            var part = package.CreatePart(partUri, contentType, compressionOption);
            package.CreateRelationship(partUri, System.IO.Packaging.TargetMode.Internal, relationshipType);

            return part;
        }

        /// <summary>
        /// Generates a unique URI with a given prefix and suffix.
        /// </summary>
        /// <param name="uriBase">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The prefix of the generated URI
        /// </para>
        /// </param>
        /// <param name="uriSuffix">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The suffix of the generated URI.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Uri"/>
        /// </para>
        /// <para>
        /// The generated URI.
        /// </para>
        /// </returns>
        private Uri GenerateUniqueUri(string uriBase, string uriSuffix)
        {
            Random rnd = new Random();
            Uri partUri;

            if (uriBase[uriBase.Length - 1] != '/')
                uriBase += "/";

            do
            {
                partUri = new Uri(uriBase + rnd.Next().ToString() + uriSuffix, UriKind.Relative);
            } while (package.PartExists(partUri));

            return partUri;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Creates a new <see cref="CompositionPart"/> with a unique URI in this package.
        /// </summary>
        /// <returns>
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The created composition part.
        /// </para>
        /// </returns>
        internal CompositionPart CreateCompositionPart()
        {
            var compositionUri = GenerateUniqueUri(PackageVocabulary.CompositionsRootUri, PackageVocabulary.CompositionPartUri);

            var implPart = CreatePartCore(
                compositionUri,
                PackageVocabulary.CompositionPartContentType,
                PackageVocabulary.CompositionPartCompression,
                PackageVocabulary.CompositionPackageRelationshipType
                );
            ProjectPart.ImplementationPart.CreateRelationship(compositionUri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.CompositionProjectRelationshipType);

            var part = new CompositionPart(this, ProjectPart, implPart, true);
            parts.Add(compositionUri, part);

            return part;
        }

        /// <summary>
        /// Creates a new <see cref="LinearCompositionViewPart"/> in this package.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The composition that owns this linear view.
        /// </para>
        /// </param>
        /// <param name="interiorCreator">
        /// <para>
        /// Type: <see cref="Elements.SectionInteriorCreator"/>
        /// </para>
        /// <para>
        /// The factory method that will create the interior of the linear view's music section.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="LinearCompositionViewPart"/>
        /// </para>
        /// <para>
        /// Created linear composition view part.
        /// </para>
        /// </returns>
        internal LinearCompositionViewPart CreateLinearCompositionViewPart(CompositionPart owner, Elements.SectionInteriorCreator interiorCreator)
        {
            var cpUriString = owner.Uri.ToString();
            cpUriString = cpUriString.Substring(0, cpUriString.LastIndexOf('/'));
            var partUri = new Uri(cpUriString + PackageVocabulary.LinearCompositionViewPartUri, UriKind.Relative);

            var implPart = CreatePartCore(
                partUri,
                PackageVocabulary.LinearCompositionViewPartContentType,
                PackageVocabulary.LinearCompositionViewPartCompressionOption,
                PackageVocabulary.LinearCompositionViewPackageRelationshipType
                );
            owner.ImplementationPart.CreateRelationship(partUri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.LinearCompositionViewCompositionRelationshipType);

            var part = new LinearCompositionViewPart(this, owner, implPart, interiorCreator);
            parts.Add(partUri, part);

            return part;
        }

        /// <summary>
        /// Creates a new <see cref="PagePart"/> in this package.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ScorePart"/>
        /// </para>
        /// <para>
        /// The score that owns this page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="PagePart"/>
        /// </para>
        /// <para>
        /// Created page part.
        /// </para>
        /// </returns>
        internal PagePart CreatePagePart(ScorePart owner)
        {
            var spUriString = owner.Uri.ToString();
            spUriString = spUriString.Substring(0, spUriString.LastIndexOf('/'));
            var partUri = GenerateUniqueUri(spUriString, ".xml");

            var implPart = CreatePartCore(
                partUri,
                PackageVocabulary.PagePartContentType,
                PackageVocabulary.PagePartCompression,
                PackageVocabulary.PagePackageRelationshipType
                );
            owner.ImplementationPart.CreateRelationship(partUri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.PageScoreRelationshipType);

            var part = new PagePart(this, owner, implPart, true);
            parts.Add(partUri, part);

            return part;
        }

        /// <summary>
        /// Creates a new <see cref="ResourcePart"/> with a unique URI in this package and writes the data of a given file inside.
        /// </summary>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name and path of the file.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="ResourcePart"/>
        /// </para>
        /// <para>
        /// The created resource part.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="System.IO.FileNotFoundException">
        /// The file identified by <paramref name="fileName"/> was not found.
        /// </exception>
        internal ResourcePart CreateResourcePart(string fileName)
        {
            if (fileName == null)
                throw new ArgumentNullException("fileName");
            if (!System.IO.File.Exists(fileName))
                throw new System.IO.FileNotFoundException();
            
            var resourceUri = GenerateUniqueUri(PackageVocabulary.ResourcesRootUri, System.IO.Path.GetExtension(fileName));

            var implPart = CreatePartCore(
                resourceUri,
                PackageVocabulary.ResourcePartContentType,
                PackageVocabulary.ResourcePartCompression,
                PackageVocabulary.ResourcePackageRelationshipType
                );
            ProjectPart.ImplementationPart.CreateRelationship(resourceUri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.ResourceProjectRelationshipType);

            var fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            var rs = implPart.GetStream(System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write);
            CopyStream(fs, rs);
            fs.Close();
            rs.Close();

            var part = new ResourcePart(this, ProjectPart, implPart);
            parts.Add(resourceUri, part);

            return part;
        }

        /// <summary>
        /// Creates a new <see cref="ScorePart"/> with a unique URI in this package.
        /// </summary>
        /// <returns>
        /// <para>
        /// Type: <see cref="ScorePart"/>
        /// </para>
        /// <para>
        /// The created score part.
        /// </para>
        /// </returns>
        internal ScorePart CreateScorePart()
        {
            var scoreUri = GenerateUniqueUri(PackageVocabulary.ScoresRootUri, PackageVocabulary.ScorePartUri);

            var implPart = CreatePartCore(
                scoreUri,
                PackageVocabulary.ScorePartContentType,
                PackageVocabulary.ScorePartCompression,
                PackageVocabulary.ScorePackageRelationshipType
                );
            ProjectPart.ImplementationPart.CreateRelationship(scoreUri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.ScoreProjectRelationshipType);

            var part = new ScorePart(this, ProjectPart, implPart, true);
            parts.Add(scoreUri, part);

            return part;
        }

        #endregion

        #region Internal Properties

        /// <summary>
        /// Gets the <see cref="System.IO.Packaging.Package"/> instance that implements
        /// the project package.
        /// </summary>
        internal System.IO.Packaging.Package ImplementationPackage
        {
            get { return package; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Saves and closes the package plus all underlying part streams.
        /// </summary>
        /// <remarks>
        /// <para>
        /// For the <see cref="Package"/> class <see cref="Close"/> and <see cref="Dispose"/> perform the same operation - 
        /// there is no reason to call <see cref="Dispose"/> if you call <see cref="Close"/> and vice-versa.
        /// </para>
        /// <para>
        /// <see cref="Close"/> and <see cref="Dispose"/> internally call <see cref="Flush"/>.
        /// </para>
        /// </remarks>
        public void Close()
        {
            Flush();
            package.Close();
            if (fileStream != null)
                fileStream.Close();
        }

        /// <summary>
        /// Deletes a part with a given URI from the package.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="partUri"/> must be a relative URI that consists of an absolute path which starts 
        /// with a forward slash ("/") character. The absolute path is relative to the package root and is 
        /// formed according to the RFC 3986 Uniform Resource Identifier (URI) Generic Syntax specification. 
        /// "/page1.xaml" and "/images/picture4.jpg" are examples of valid part URIs.
        /// </para>
        /// <para>
        /// No exception is raised if a part with the specified partUri is not in the package. 
        /// (You can use the <see cref="PartExists"/> method to determine whether a part with a specified partUri is in the package.)
        /// </para>
        /// <para>
        /// If the part have logical child parts, they are deleted as well (e.g. when a <see cref="ScorePart"/> is deleted, all the <see cref="PagePart"/>s
        /// that represent the pages of this score are removed from the package.
        /// </para>
        /// <para>
        /// For additional information, see the Open Packaging Conventions (OPC) specification available for download at http://go.microsoft.com/fwlink/?LinkID=71255.
        /// </para>
        /// </remarks>
        /// <param name="partUri">
        /// <para>
        /// Type: <see cref="System.Uri"/>
        /// </para>
        /// <para>
        /// The URI of the part to delete.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partUri"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="partUri"/> is not a valid <see cref="PackagePart"/> uniform resource identifier (URI).
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// A part with the specified <paramref name="partUri"/> is not in the package.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The package is not open (<see cref="Dispose"/> or <see cref="Close"/> has been called).
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// The package is read-only.
        /// </exception>
        public void DeletePart(Uri partUri)
        {
            if (!package.PartExists(partUri))
                return;

            PackagePart deletedPart = GetPart(partUri);

            // First delete all the logical child parts of the deleted part
            var deletedChildren = new List<Uri>();
            foreach (System.IO.Packaging.PackageRelationship relship in deletedPart.ImplementationPart.GetRelationships())
                deletedChildren.Add(relship.TargetUri);
            foreach (Uri uri in deletedChildren)
                DeletePart(uri);

            parts.Remove(partUri);
            package.DeletePart(partUri);
            var removedRelships = new List<System.IO.Packaging.PackageRelationship>();

            // Remove all relationships from the package to the deleted part
            foreach (System.IO.Packaging.PackageRelationship relship in package.GetRelationships())
            {
                if (relship.TargetUri == partUri)
                    removedRelships.Add(relship);
            }
            foreach (System.IO.Packaging.PackageRelationship relship in removedRelships)
                package.DeleteRelationship(relship.Id);
            removedRelships.Clear();

            // Remove all relationships from the deleted part's owner
            if (deletedPart.Owner != null)
            {
                foreach (System.IO.Packaging.PackageRelationship relship in deletedPart.Owner.ImplementationPart.GetRelationships())
                {
                    if (relship.TargetUri == partUri)
                        removedRelships.Add(relship);
                }
                foreach (System.IO.Packaging.PackageRelationship relship in removedRelships)
                    deletedPart.Owner.ImplementationPart.DeleteRelationship(relship.Id);
                removedRelships.Clear();
            }

            IsModified = true;
        }

        /// <summary>
        /// Saves the contents of all parts and relationships that are contained in the package.
        /// </summary>
        /// <remarks>
        /// <see cref="Flush"/> is called automatically by <see cref="Close"/> and <see cref="Dispose"/>.
        /// When <see cref="Close"/> or <see cref="Dispose"/> is called you do not have to call <see cref="Flush"/> separately.
        /// </remarks>
        /// <exception cref="ObjectDisposedException">
        /// The package is not open (<see cref="Dispose"/> or <see cref="Close"/> has been called).
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// The package is read-only and cannot be modified.
        /// </exception>
        public void Flush()
        {
            foreach (PackagePart part in parts.Values)
                part.Flush();

            package.Close();
            if (fileStream != null)
            {
                fileStream.Flush();
                this.package = System.IO.Packaging.Package.Open(fileStream, fileMode, fileAccess);
            }
            else
                this.package = System.IO.Packaging.Package.Open(fileName, fileMode, fileAccess, fileShare);

            foreach (var part in parts)
                part.Value.ReplaceImplementationPart(package.GetPart(part.Key));

            IsModified = false;
        }

        /// <summary>
        /// Returns the part with a given URI.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="partUri"/> must be a relative URI that consists of an absolute path which starts 
        /// with a forward slash ("/") character. The absolute path is relative to the package root and is 
        /// formed according to the RFC 3986 Uniform Resource Identifier (URI) Generic Syntax specification. 
        /// "/page1.xaml" and "/images/picture4.jpg" are examples of valid part URIs.
        /// </para>
        /// <para>
        /// For additional information, see the Open Packaging Conventions (OPC) specification available for download at http://go.microsoft.com/fwlink/?LinkID=71255.
        /// </para>
        /// </remarks>
        /// <param name="partUri">
        /// <para>
        /// Type: <see cref="System.Uri"/>
        /// </para>
        /// <para>
        /// The uniform resource identifier (URI) of the part to return.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="PackagePart"/>
        /// </para>
        /// <para>
        /// The part with the specified <paramref name="partUri"/>.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partUri"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="partUri"/> is not a valid <see cref="PackagePart"/> uniform resource identifier (URI).
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// A part with the specified <paramref name="partUri"/> is not in the package.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The package is not open (<see cref="Dispose"/> or <see cref="Close"/> has been called).
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// The package is write-only.
        /// </exception>
        /// <exception cref="WrongPackageFormatException">
        /// The content type of the part specified by <paramref name="partUri"/> is not recognized.
        /// </exception>
        public PackagePart GetPart(Uri partUri)
        {
            PackagePart part;

            if (parts.TryGetValue(partUri, out part))
                return part;

            var implPart = package.GetPart(partUri);
            
            switch (implPart.ContentType)
            {
                case PackageVocabulary.ScorePartContentType:
                    part = new ScorePart(this, ProjectPart, implPart, false);
                    break;
                case PackageVocabulary.PagePartContentType:
                {
                    string baseUri = implPart.Uri.ToString();
                    baseUri = baseUri.Substring(0, baseUri.LastIndexOf('/'));
                    var ownerUri = new Uri(baseUri + PackageVocabulary.ScorePartUri, UriKind.Relative);

                    var owner = (ScorePart)GetPart(ownerUri);
                    part = new PagePart(this, owner, implPart, false);
                    break;
                }
                case PackageVocabulary.ResourcePartContentType:
                    part = new ResourcePart(this, ProjectPart, implPart);
                    break;
                case PackageVocabulary.CompositionPartContentType:
                    part = new CompositionPart(this, ProjectPart, implPart, false);
                    break;
                case PackageVocabulary.LinearCompositionViewPartContentType:
                {
                    string baseUri = implPart.Uri.ToString();
                    baseUri = baseUri.Substring(0, baseUri.LastIndexOf('/'));
                    var ownerUri = new Uri(baseUri + PackageVocabulary.CompositionPartUri, UriKind.Relative);

                    var owner = (CompositionPart)GetPart(ownerUri);
                    part = new LinearCompositionViewPart(this, owner, implPart);
                    break;
                }
                default:
                    throw new WrongPackageFormatException(
                        WrongPackageFormatException.ExceptionType.CorruptedPackage,
                        String.Format("The part at {0} has an unknown content type {1}!",
                            partUri.ToString(),
                            implPart.ContentType
                            )
                        );
            }

            parts.Add(part.Uri, part);
            return part;
        }

        /// <summary>
        /// Opens a package at a given path and file name.
        /// </summary>
        /// <remarks>
        /// This <see cref="Open(string)"/> method opens the package with default attributes 
        ///     <see cref="System.IO.FileMode.OpenOrCreate"/>,
        ///     <see cref="System.IO.FileAccess.ReadWrite"/>
        /// and
        ///     <see cref="System.IO.FileShare.Read"/>
        /// (To specify diffent attributes use one of the other Open method overloads).
        /// </remarks>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The path and file name of the package
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The opened package
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileName"/> is <see langword="null"/>.
        /// </exception>
        public static Package Open(string fileName)
        {
            var stream = new System.IO.FileStream(fileName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, System.IO.FileShare.Read);

            return new Package(System.IO.Packaging.Package.Open(stream, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite), stream)
            {
                fileAccess = System.IO.FileAccess.ReadWrite,
                fileMode = System.IO.FileMode.OpenOrCreate,
                fileName = fileName,
                fileShare = System.IO.FileShare.Read
            };
        }

        /// <summary>
        /// Opens a package at a given path using a given file mode.
        /// </summary>
        /// <remarks>
        /// This <see cref="Open(string, System.IO.FileMode)"/> method opens the package with default attributes 
        ///     <see cref="System.IO.FileAccess.ReadWrite"/>
        /// and
        ///     <see cref="System.IO.FileShare.Read"/>
        /// (To specify diffent attributes use one of the other Open method overloads).
        /// </remarks>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The path and file name of the package
        /// </para>
        /// </param>
        /// <param name="packageMode">
        /// <para>
        /// Type: <see cref="System.IO.FileMode"/>
        /// </para>
        /// <para>
        /// The file mode in which to open the package.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The opened package
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Value for <paramref name="packageMode"/> is not valid.
        /// </exception>
        public static Package Open(string fileName, System.IO.FileMode packageMode)
        {
            var stream = new System.IO.FileStream(fileName, packageMode, System.IO.FileAccess.ReadWrite, System.IO.FileShare.Read);

            return new Package(System.IO.Packaging.ZipPackage.Open(stream, packageMode, System.IO.FileAccess.ReadWrite), stream)
            {
                fileAccess = System.IO.FileAccess.ReadWrite,
                fileMode = packageMode,
                fileName = fileName,
                fileShare = System.IO.FileShare.Read
            };
        }

        /// <summary>
        /// Opens a package at a given path using a given file mode and file access setting.
        /// </summary>
        /// <remarks>
        /// This <see cref="Open(string, System.IO.FileMode, System.IO.FileAccess)"/> method opens the package with a default <see cref="System.IO.FileShare.Read"/> attribute
        /// (To specify a diffent attribute use one of the other Open method overloads).
        /// </remarks>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The path and file name of the package
        /// </para>
        /// </param>
        /// <param name="packageMode">
        /// <para>
        /// Type: <see cref="System.IO.FileMode"/>
        /// </para>
        /// <para>
        /// The file mode in which to open the package.
        /// </para>
        /// </param>
        /// <param name="packageAccess">
        /// <para>
        /// Type: <see cref="System.IO.FileAccess"/>
        /// </para>
        /// <para>
        /// The file access in which to open the package.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The opened package
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Value for <paramref name="packageMode"/> or <paramref name="packageAccess"/> is not valid.
        /// </exception>
        public static Package Open(string fileName, System.IO.FileMode packageMode, System.IO.FileAccess packageAccess)
        {
            var stream = new System.IO.FileStream(fileName, packageMode, packageAccess, System.IO.FileShare.Read);

            return new Package(System.IO.Packaging.ZipPackage.Open(stream, packageMode, packageAccess), stream)
            {
                fileAccess = packageAccess,
                fileMode = packageMode,
                fileName = fileName,
                fileShare = System.IO.FileShare.Read
            };
        }

        /// <summary>
        /// Opens a package at a given path using a given file mode, file access and file share setting.
        /// </summary>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The path and file name of the package
        /// </para>
        /// </param>
        /// <param name="packageMode">
        /// <para>
        /// Type: <see cref="System.IO.FileMode"/>
        /// </para>
        /// <para>
        /// The file mode in which to open the package.
        /// </para>
        /// </param>
        /// <param name="packageAccess">
        /// <para>
        /// Type: <see cref="System.IO.FileAccess"/>
        /// </para>
        /// <para>
        /// The file access in which to open the package.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The opened package
        /// </para>
        /// <param name="packageShare">
        /// <para>
        /// Type: <see cref="System.IO.FileShare"/>
        /// </para>
        /// <para>
        /// The file sharing mode in which to open the package.
        /// </para>
        /// </param>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Value for <paramref name="packageMode"/>, <paramref name="packageAccess"/> or <paramref name="packageShare"/> is not valid.
        /// </exception>
        public static Package Open(string fileName, System.IO.FileMode packageMode, System.IO.FileAccess packageAccess, System.IO.FileShare packageShare)
        {
            return new Package(System.IO.Packaging.ZipPackage.Open(fileName, packageMode, packageAccess, packageShare))
            {
                fileAccess = packageAccess,
                fileMode = packageMode,
                fileName = fileName,
                fileShare = packageShare
            };
        }

        /// <summary>
        /// Indicates whether a part with a given URI is in the package.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="partUri"/> must be a relative URI that consists of an absolute path which starts 
        /// with a forward slash ("/") character. The absolute path is relative to the package root and is 
        /// formed according to the RFC 3986 Uniform Resource Identifier (URI) Generic Syntax specification. 
        /// "/page1.xaml" and "/images/picture4.jpg" are examples of valid part URIs.
        /// </para>
        /// <para>
        /// For additional information, see the Open Packaging Conventions (OPC) specification available for download at http://go.microsoft.com/fwlink/?LinkID=71255.
        /// </para>
        /// </remarks>
        /// <param name="partUri">
        /// <para>
        /// Type: <see cref="System.Uri"/>
        /// </para>
        /// <para>
        /// The URI of the part to check for.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// <see langword="true"/> if a part with the specified <paramref name="partUri"/> is in the package, otherwise <see langword="false"/>
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partUri"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="partUri"/> is not a valid <see cref="PackagePart"/> uniform resource identifier (URI).
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The package is not open (<see cref="Dispose"/> or <see cref="Close"/> has been called).
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// The package is write-only (information cannot be read).
        /// </exception>
        public bool PartExists(Uri partUri)
        {
            return package.PartExists(partUri);
        }

        /// <summary>
        /// Reinserts a part that was previously deleted back to the package.
        /// </summary>
        /// <param name="part">
        /// <para>
        /// Type: <see cref="PackagePart"/>
        /// </para>
        /// <para>
        /// The resotred package part.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="part"/> was not a part of this package.<br/>
        /// -or-<br/>
        /// A part with the same URI already exists in the package.
        /// </exception>
        public void RestorePart(PackagePart part)
        {
            if (part == null)
                throw new ArgumentNullException("part");
            if (ImplementationPackage.PartExists(part.Uri))
                throw new ArgumentException("A part with the same URI already exists in the package.");
            if (part.Package != this)
                throw new ArgumentException("The part was not a part of this package.");

            var implPart = ImplementationPackage.CreatePart(part.Uri, part.ContentType, part.CompressionOption);
            part.ReplaceImplementationPart(implPart);
            parts[part.Uri] = part;

            if (part is ResourcePart)
            {
                ImplementationPackage.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.ResourcePackageRelationshipType);
                ProjectPart.ImplementationPart.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.ResourceProjectRelationshipType);
            }
            else if (part is ScorePart)
            {
                ImplementationPackage.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.ScorePackageRelationshipType);
                ProjectPart.ImplementationPart.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.ScoreProjectRelationshipType);
            }
            else if (part is CompositionPart)
            {
                ImplementationPackage.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.CompositionPackageRelationshipType);
                ProjectPart.ImplementationPart.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.CompositionProjectRelationshipType);
            }
            else if (part is PagePart)
            {
                ImplementationPackage.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.PagePackageRelationshipType);
                var scorePart = (ScorePart)part.Owner;
                scorePart.ImplementationPart.CreateRelationship(part.Uri, System.IO.Packaging.TargetMode.Internal, PackageVocabulary.PageScoreRelationshipType);
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the core properties of the package.
        /// </summary>
        /// <remarks>
        /// For additional information about core package properties, see section 3.1 in the Open Packaging Conventions (OPC) 
        /// specification available for download at http://go.microsoft.com/fwlink/?LinkID=71255.
        /// </remarks>
        /// <value>
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackageProperties"/>
        /// </para>
        /// <para>
        /// The core properties of the package.
        /// </para>
        /// </value>
        /// <exception cref="ObjectDisposedException">
        /// The package is not open (<see cref="Dispose"/> or <see cref="Close"/> has been called).
        /// </exception>
        public System.IO.Packaging.PackageProperties CoreProperties
        {
            get { return package.PackageProperties; }
        }

        /// <summary>
        /// Gets the ID authority for this package.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="IDAuthority"/>
        /// </para>
        /// <para>
        /// The ID authority responsible for assigning IDs to objects owned by this package.
        /// The IDs are used to serialize / deserialize object references.
        /// </para>
        /// </value>
        public IDAuthority IDMap
        {
            get { return idAuthority; }
        }

        /// <summary>
        /// Gets a value indicating whether this package has unsaved changes or not.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the package has unsaved changes or not.
        /// </para>
        /// </value>
        public bool IsModified
        {
            get { return isModified; }
            set
            {
                isModified = value;
                
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("IsModified"));
            }
        }

        /// <summary>
        /// Gets the one and only <see cref="ProjectPart"/> of the <see cref="Package"/>.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ProjectPart"/>
        /// </para>
        /// <para>
        /// The package always contains exactly one <see cref="ProjectPart"/>.
        /// This property holds a reference to it.
        /// </para>
        /// </value>
        public ProjectPart ProjectPart
        {
            get { return projectPart; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the access type to the package file.
        /// </summary>
        private System.IO.FileAccess fileAccess;
        /// <summary>
        /// Holds the mode of the package file.
        /// </summary>
        private System.IO.FileMode fileMode;
        /// <summary>
        /// Holds the name of the package file.
        /// </summary>
        private string fileName;
        /// <summary>
        /// Holds the share mode of the package file.
        /// </summary>
        private System.IO.FileShare fileShare;
        /// <summary>
        /// Holds the stream to the package file.
        /// </summary>
        private System.IO.Stream fileStream;
        /// <summary>
        /// Holds the ID authority.
        /// </summary>
        private IDAuthority idAuthority;
        /// <summary>
        /// Indicates whether the package has unsaved changes or not.
        /// </summary>
        private bool isModified;
        /// <summary>
        /// Holds the implementation package.
        /// </summary>
        private System.IO.Packaging.Package package;
        /// <summary>
        /// Holds a dictionary of open package parts (mapped by their URI).
        /// </summary>
        private Dictionary<Uri, PackagePart> parts;
        /// <summary>
        /// Holds the one and only project part instance.
        /// </summary>
        private ProjectPart projectPart;

        #endregion
    }
}
